﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using MathService.Vectors;

namespace MathService.Interpolation
{
    /// <summary><see cref="http://ru.wikipedia.org/wiki/Кривая_Безье">Кривая Безье</see></summary>
    [Hyperlink("http://ru.wikipedia.org/wiki/Кривая_Безье")]
    public class BezierCurve
    {
        /* -------------------------------------------------------------------------------------------- */

        /// <summary>
        /// Биномиальный коэффициент (1+x)^n из <paramref name="n"/> по <paramref name="k"/>
        /// <see cref="http://ru.wikipedia.org/wiki/Биномиальный_коэффициент">Википедия:Биномиальный коэффициент</see>>
        /// </summary>
        /// <param name="n">Степень <see cref="http://ru.wikipedia.org/wiki/Бином_Ньютона">бинома Ньютона</see>></param>
        /// <param name="k">Номер коэффициента</param>
        /// <returns>Коэффициент разложения Бинома Ньютона (1+x)^n</returns>
        [Hyperlink("http://ru.wikipedia.org/wiki/Биномиальный_коэффициент")]
        private static int BinomCoefficient(int n, int k)
        {
            if(k < 0 || (0 <= n && n < k)) return 0;
            var K = 1L;
            if(n < 0 && 0 <= K)
            {
                K = (k % 2) == 0 ? 1 : -1;
                n = -n + k - 1;
            }
            return (int)(K * n.Factorial() / (k.Factorial() - (n - k).Factorial()));
        }

        /// <summary>Получить <see cref="http://ru.wikipedia.org/wiki/Многочлен_Бернштейна">Полином Бернштейна</see>></summary>
        /// <param name="k">Номер многочлена</param>
        /// <param name="n">Степень</param>
        /// <returns></returns>
        [Hyperlink("http://ru.wikipedia.org/wiki/Многочлен_Бернштейна")]
        private static Func<double, double> GetBernshteynPolynom(int k, int n)
        {
            var B = BinomCoefficient(n, k);
            var I = (double)k;
            var K = (double)(n - k);
            return t => B * Math.Pow(t, I) * Math.Pow((1 - t), K);
        }

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Набор точек аппроксимации</summary>
        private Vector2D[] f_Points;

        /// <summary><see cref="http://ru.wikipedia.org/wiki/Многочлен_Бернштейна">Полином Бернштейна</see>></summary>        
        private Func<double, double>[] f_BernshteynPolynoms;

        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Новая <see cref="http://ru.wikipedia.org/wiki/Кривая_Безье">кривая Безье</see></summary>
        /// <param name="X">Список координато точек x</param>
        /// <param name="Y">Список кординат точек y</param>
        public BezierCurve(IEnumerable<double> X, IEnumerable<double> Y)
        {
            Contract.Requires(X.Count() == Y.Count());

            var x = X.Select((xx, i) => new { X = xx, i });
            var y = Y.Select((yy, i) => new { Y = yy, i });

            Initialize(x.Join(y, xx => xx.i, yy => yy.i, (xx, yy) => new Vector2D(xx.X, yy.Y)));

            //var lv_Points = new Vector2D[X.Count];
            //for(var i = 0; i < X.Count; i++)
            //    lv_Points[i] = new Vector2D(X[i], Y[i]);
            //Initialize(lv_Points);
        }

        /// <summary>Новая <see cref="http://ru.wikipedia.org/wiki/Кривая_Безье">кривая Безье</see></summary>
        /// <param name="Points">Набор точек в виде <see cref="MathService.Complex">комплексных чисел</see></param>
        public BezierCurve(IEnumerable<Complex> Points)
        {
            Contract.Requires(Points != null);

            Initialize(Points.Select(c => (Vector2D)c));
        }

        /// <summary>Новая <see cref="http://ru.wikipedia.org/wiki/Кривая_Безье">кривая Безье</see></summary>
        /// <param name="Points">Набор точек</param>
        public BezierCurve(IEnumerable<Vector2D> Points)
        {
            Contract.Requires(Points != null);

            Initialize(Points);
        }

        /// <summary>Инициализировать кривую Безье</summary>
        /// <param name="Points">Набор точек</param>
        private void Initialize(IEnumerable<Vector2D> Points)
        {
            f_Points = Points.ToArray();
            var lv_N = f_Points.Length;
            f_BernshteynPolynoms = new Func<double, double>[lv_N];
            for(var i = 0; i < lv_N; i++)
                f_BernshteynPolynoms[i] = GetBernshteynPolynom(i, lv_N);
        }

        /* -------------------------------------------------------------------------------------------- */

        public Vector2D B(double t)
        {
            Contract.Requires(t >= 0);
            Contract.Requires(t <= 1);

            if(t < 0 || t > 1)
                throw new ArgumentOutOfRangeException("t", "t в не интервала [0;1]");

            var lv_Result = new Vector2D();
            var lv_N = f_Points.Length;
            for(var i = 0; i < lv_N; i++)
            {
                lv_Result.X += f_Points[i].X * f_BernshteynPolynoms[i](t);
                lv_Result.Y += f_Points[i].Y * f_BernshteynPolynoms[i](t);
            }
            return lv_Result;
        }

        /* -------------------------------------------------------------------------------------------- */
    }
}
